﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Diagnostics;
using System.Threading;
using System.Net;

namespace Master
{
    /*
     * Map holds and keeps track of all elements that occur within the simulation.
     * 
     * Map is a Canvas that implements the singleton pattern and has the ability to search for specific elements that occur on a specific location.
     * The singleton pattern has been slightly altered to keep the Map compatible with XAML (which requires the default constructor)
     * 
     * @Author A.J. Janssen
     * @Changes made by:
     *      - T.T. Dusseldorp
     * */
    public class Map : Canvas
    {
        /*************************
         * DEPENDENCY PROPERTIES *
         *************************/
        public static readonly DependencyProperty OrientationProperty;

        /**
         * Orientation of the map, can be used to rotate the entire map
         * */
        public int Orientation
        {
            get { return (int)this.GetValue(OrientationProperty); }
            set { this.SetValue(OrientationProperty, value); }
        }

        /*****************
         * STATIC FIELDS *
         *****************/
        public static Map INSTANCE; //Singleton INSTANCE of the Map
        private static Boolean initialized = false;

        /**********************
         * STATIC CONSTRUCTOR *
         **********************/
        static Map() {
            FrameworkPropertyMetadata OrientationPropertyMetaData = new FrameworkPropertyMetadata(0, new PropertyChangedCallback(Map.RotationPropertyChanged));
                    OrientationProperty = DependencyProperty.Register("Orientation",
            typeof(int), typeof(Map), OrientationPropertyMetaData);
        }

        private static void RotationPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            updateOrientations();
        }

        /**********
         * FIELDS *
         **********/
        private CarManager carManager;

        /***************
         * CONSTRUCTOR *
         ***************/
        public Map()
            : base()
        {
            //if (Map.initialized)
             //   throw new InvalidOperationException();
            INSTANCE = this;
            Map.initialized = true;
            carManager = CarManager.getInstance();
            NetworkManager networkManager = NetworkManager.INSTANCE;
        }

        /********************
         * PUBLIC FUNCTIONS *
         ********************/
        /**
         * Method to add a new (random) car at the given spawnpoint
         * */
        public void addCar(SpawnPoint spawnPoint)
        {
            carManager.addCar(spawnPoint);
        }

        /**
         * Method to add the given amount of (random) cars at the speficied spawnpoint
         * */
        public void addCar(SpawnPoint spawnPoint, int numberOfCars)
        {
            carManager.addCar(spawnPoint, numberOfCars);
        }

        /**
         * Method to remove the specified car
         * */
        public void removeCar(Car car)
        {
            carManager.removeCar(car);
        }
        
        /**
         * Method to retrieve the first car found at the given point
         * Returns null if no cars are located at this point
         * */
        public Car getCarAtPoint(Point p)
        {
            return carManager.getCarAtPoint(p);
        }

        /**
         * Method to retrieve all cars that fall within the given bounds
         * Returns a empty list if no cars are found
         * */
        public List<Car> getCarsAtBounds(Rect bounds)
        {
            return carManager.getCarsAtBounds(bounds);
        }

        /**
         * Returns the first element found at this point, or null if no elements are located here.
         * */
        public UIElement getElementAtPoint(Point p)
        {
            foreach (UIElement nextElement in Children)
            {
                if (!(nextElement is FrameworkElement)) 
                    continue;
                if (Toolkit.elementIsAtPoint(Toolkit.getPointRelativeToMap(nextElement), (FrameworkElement)nextElement, p))
                    return nextElement;
            }
            return null;
        }

        /**
         * Returns the road located at the given point, or null if no roads are located here
         * */
        public Road getRoadAtPoint(Point p)
        {
            foreach (UIElement nextElement in Children)
            {
                if (nextElement is Road && Toolkit.elementIsAtPoint(Toolkit.getPointRelativeToMap(nextElement), (FrameworkElement)nextElement, p))
                    return (Road)nextElement;
            }
            return null;
        }

        /**
         * Returns the NpOutgoing located at the given point, or null if no NpOutgoing-points are located here
         * */
        public NpOutgoing getNpOutgoingAtPoint(Point p)
        {
            foreach (UIElement nextElement in Children)
            {
                if (nextElement is NpOutgoing && Toolkit.elementIsAtPoint(Toolkit.getPointRelativeToMap(nextElement), (FrameworkElement)nextElement, p))
                    return (NpOutgoing)nextElement;
            }
            return null;
        }

        /**
         * Method to retrieve ALL NpOutgoingPoints
         * */
        public List<NpOutgoing> getNpOutgoingPoints()
        {
            List<NpOutgoing> resultList = new List<NpOutgoing>();
            foreach (UIElement nextElement in Children) if (nextElement is NpOutgoing)
                    resultList.Add((NpOutgoing)nextElement);
            return resultList;
        }

        /**
         * Method to retrieve whatever lane is located at the specified point, or null if no lanes are located here
         * */
        public Lane getLaneAtPoint(Point p)
        {
            foreach (UIElement nextElement in Children)
            {
                if (!(nextElement is FrameworkElement)) continue;
                FrameworkElement frameWorkElement = (FrameworkElement)nextElement;
                if (!(Toolkit.elementIsAtPoint(Toolkit.getPointRelativeToMap(frameWorkElement), frameWorkElement, p))) 
                    continue;
                if (frameWorkElement is Lane) return (Lane) frameWorkElement;
                if (frameWorkElement is Road)
                {
                    Panel panel = (Panel)frameWorkElement;
                    foreach (UIElement child in panel.Children)
                    {
                        if (!(child is Lane)) continue;
                        if (Toolkit.elementIsAtPoint(Toolkit.getPointRelativeToMap(child), (Lane)child, p))
                            return (Lane)child;
                    }
                }
            }
            return null;
        }

        /**
         * Method to retrieve the intersection located at the given point, or null if no intersections are located here
         * */
        public InterSection getInterSectionAtPoint(Point p)
        {
            foreach (UIElement nextElement in Children)
            {
                if (nextElement is InterSection && Toolkit.elementIsAtPoint(Toolkit.getPointRelativeToMap(nextElement), (FrameworkElement)nextElement, p))
                    return (InterSection)nextElement;
            }
            return null;
        }

        /**
         * Method to retrieve the intersection located at the given bounds, or null if no intersections are located here
         * */
        public InterSection getIntersectionAtBounds(Rect bounds)
        {
            foreach (UIElement nextElement in Children)
            {
                if (nextElement is InterSection && Toolkit.elementIsAtBounds((FrameworkElement) nextElement, bounds))
                    return (InterSection)nextElement;
            }
            return null;
        }

        /**
         * Method to retrieve ALL spawnpoints
         * */
        public List<SpawnPoint> getSpawnPoints()
        {
            List<SpawnPoint> resultList = new List<SpawnPoint>();
            foreach (UIElement nextElement in Children)
            {
                if (nextElement is SpawnPoint)
                    resultList.Add((SpawnPoint)nextElement);
            }
            return resultList;
        }

        private static void updateOrientations() //method to update the current orientation (rotates the map)
        {
            Map.INSTANCE.LayoutTransform = new RotateTransform((int)Map.INSTANCE.GetValue(OrientationProperty));
        }

        /**
         * Method to add a label in the TopLeft corner showing the local machine's IP Adress
         * */
        public void printIpAdress()
        {
            IPHostEntry hostEntry = Dns.GetHostEntry(Dns.GetHostName());
            String ip = (
                       from addr in hostEntry.AddressList
                       where addr.AddressFamily.ToString() == "InterNetwork"
                       select addr.ToString()
                ).FirstOrDefault();

            // Create label for ip
            Label ipLabel = new Label();
            ipLabel.Foreground = Brushes.White;
            ipLabel.Background = Brushes.Gray;
            ipLabel.Content = "Your ip is: " + ip;
            Map.INSTANCE.Children.Add(ipLabel);
        }
    }
}
